// $Id$ 
package de.servicezombie.cxf;

import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.servicezombie.cxf.jaxb.AnnotatedClassScanner;

public class CachingJaxbContextResolver implements ContextResolver<JAXBContext> {

	private static final Logger log = LoggerFactory.getLogger(CachingJaxbContextResolver.class);

	/**
	 * use the given ContextResolver to get a new jaxb context.
	 * if failed null is returned or a runtime exception might be thrown.
	 */
	public static JAXBContext getJAXBContext(ContextResolver<JAXBContext> contextResolver, Class<?> type) {
		if (log.isDebugEnabled()) {
			log.debug("getJAXBContext: >> " + type);
		}

		JAXBContext context = null;

		if (contextResolver != null) {
			if (log.isDebugEnabled()) {
				log.debug("getJAXBContext: using custom context resolver");
			}

			context = contextResolver.getContext(type);
		}

		if (log.isDebugEnabled()) {
			log.debug("getJAXBContext: << " + context);
		}
		return context;
	}

	private JAXBContext jaxbContext;
	private Class<?>[] classes;

	/**
	 * build a new jaxb context based on classes properties
	 */
	private JAXBContext buildJAXBContext() {

		if (log.isDebugEnabled()) {
			log.debug("buildJAXBContext: build context based on " + ArrayUtils.toString(classes));
		}

		if (!isClassesAvailable()) {
			throw new IllegalStateException("Set classes property for creating a jaxbcontext");
		}

		JAXBContext context = null;

		try {
			context = JAXBContext.newInstance(classes);
		}
		catch (JAXBException e) {
			throw new IllegalStateException(e);
		}
		assert context != null;
		return context;
	}

	private boolean isClassesAvailable() {
		return classes != null && classes.length > 0;
	}

	public void setJaxbContext(JAXBContext jaxbContext) {
		this.jaxbContext = jaxbContext;
	}

	public void setClasses(Class<?>[] classes) {
		AnnotatedClassScanner scanner = new AnnotatedClassScanner();
		this.classes = scanner.scan(classes);
	}

	/**
	 * get a jaxbcontext, if class is contained in classes, otherwise null
	 * The jaxbcontext is implemented as a singleton
	 */
	public JAXBContext getContext(Class<?> type) {
		if (log.isDebugEnabled()) {
			log.debug("getJAXBContext: >> " + type);
		}

		// if we are not responsible, return null
		if (!ArrayUtils.contains(classes, type)) {
			return null;
		}

		synchronized (this) {
			if (jaxbContext == null) {
				jaxbContext = buildJAXBContext();
			}
		}

		return jaxbContext;
	}

}
